package com.fast.framework.config.security.jwt;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.fast.framework.config.redis.RedisCache;
import com.fast.framework.constant.CacheConstants;
import com.fast.framework.constant.Constants;
import com.fast.framework.core.domain.User;
import com.fast.framework.enums.JwtEnum;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.fast.framework.enums.JwtEnum.tokenHead;
import static com.fast.framework.enums.JwtEnum.tokenHeader;

/**
 * <p>
 * jwt token的配置类
 * </p>
 *
 * @author lanyuankeji
 * @since 2024-04-02
 */
@Component("jwtTokenUtil")
public class JwtTokenUtil {
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
    public static final String LOGIN_USER_KEY = "login_user_key";
    protected static final long MILLIS_SECOND = 1000;
    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
    private static final String secret = JwtEnum.secret.getValue();
    private static final int expiration = Integer.parseInt(JwtEnum.expiration.getValue());
    private static final Gson gson = new Gson();
    @Resource
    private RedisCache redisCache;

    /**
     * 根据用户信息生成token
     *
     * @param loginUser 用户详细信息
     * @return token信息
     */
    public String generateToken(User loginUser) {
        //刷新token
        refreshToken(loginUser);
        //生成token
        Map<String, Object> claims = new HashMap<>();
        claims.put(LOGIN_USER_KEY, loginUser.getUsername());
        return generateToken(claims);
    }

    /**
     * 根据负载生成token
     *
     * @param claims 负载
     * @return token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token token
     * @return 数据声明
     */
    public Claims parseToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * 根据请求头里的token去获取当前登录的用户
     *
     * @param request http 请求
     * @return 当前登录用户
     */
    public User getLoginUser(HttpServletRequest request) throws AccessDeniedException {
        // 获取请求携带的令牌
        String token = request.getHeader(tokenHeader.getValue());
        // 不存在 token
        if (null != token && token.startsWith(tokenHead.getValue())) {
            if (!StringUtils.hasText(token.substring(tokenHead.getValue().length()))) {
                throw new AccessDeniedException("用户未登录！！！");
            }
            // 存在 token
            token = token.substring(tokenHead.getValue().length());
            if (StrUtil.isNotEmpty(token)) {
                try {
                    // 解析对应的权限以及用户信息
                    Claims claims = parseToken(token);
                    String username = (String) claims.get(Constants.LOGIN_USER_KEY);
                    String cacheObject = redisCache.getCacheObject(getTokenKey(username));
                    //缓存中存储的登录用户数据
                    return gson.fromJson(cacheObject, User.class);
                } catch (Exception e) {
                    logger.error("获取用户信息异常'{}'", e.getMessage());
                }
            }
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(User loginUser) {
        if (ObjUtil.isNotNull(loginUser) && StrUtil.isNotEmpty(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token) {
        if (StrUtil.isNotEmpty(token)) {
            redisCache.deleteObject(getTokenKey(token));
        }
    }

    /**
     * 根据登录用户去验证token，验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser 登录用户
     */
    public void verifyToken(User loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新登录用户令牌有效期
     *
     * @param loginUser 登录用户
     */
    public void refreshToken(User loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expiration * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getUsername());
        redisCache.setCacheObject(userKey, gson.toJson(loginUser), expiration, TimeUnit.MINUTES);
    }

    /**
     * 生成缓存的key
     *
     * @param username 用户账号
     * @return 缓存的key
     */
    private String getTokenKey(String username) {
        return CacheConstants.LOGIN_TOKEN_KEY + username;
    }
}
